<html><head><meta charset="utf-8"><title>39 经验总结：不同场景，如何使用线程池     -慕课专栏</title>
			<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
			<meta name="renderer" content="webkit">
			<meta property="qc:admins" content="77103107776157736375">
			<meta property="wb:webmaster" content="c4f857219bfae3cb">
			<meta http-equiv="Access-Control-Allow-Origin" content="*">
			<meta http-equiv="Cache-Control" content="no-transform ">
			<meta http-equiv="Cache-Control" content="no-siteapp">
			<link rel="apple-touch-icon" sizes="76x76" href="https://www.imooc.com/static/img/common/touch-icon-ipad.png">
			<link rel="apple-touch-icon" sizes="120x120" href="https://www.imooc.com/static/img/common/touch-icon-iphone-retina.png">
			<link rel="apple-touch-icon" sizes="152x152" href="https://www.imooc.com/static/img/common/touch-icon-ipad-retina.png">
			<link href="https://moco.imooc.com/captcha/style/captcha.min.css" rel="stylesheet">
			<link rel="stylesheet" href="https://www.imooc.com/static/moco/v1.0/dist/css/moco.min.css?t=201907021539" type="text/css">
			<link rel="stylesheet" href="https://www.imooc.com/static/lib/swiper/swiper-3.4.2.min.css?t=201907021539">
			<link rel="stylesheet" href="https://static.mukewang.com/static/css/??base.css,common/common-less.css?t=2.5,column/zhuanlanChapter-less.css?t=2.5,course/inc/course_tipoff-less.css?t=2.5?v=201907051055" type="text/css">
			<link charset="utf-8" rel="stylesheet" href="https://www.imooc.com/static/lib/ueditor/themes/imooc/css/ueditor.css?v=201907021539"><link rel="stylesheet" href="https://www.imooc.com/static/lib/baiduShare/api/css/share_style0_16.css?v=6aba13f0.css"></head>
			<body><div id="main">


<div class="main-con hide-menu">
    <!-- 左侧菜单 & 索引 -->
    
    <div class="right-content" style="padding-left: 0px;">
        <div class="container clearfix" id="top" style="width: 1134px; display: block;">
            
            
            <div class="center_con js-center_con l" style="width: 1134px;">
                <div class="article-con">
                                            <!-- 买过的阅读 -->
                        

                    
                    <div class="art-title" style="margin-top: 0px;">
                        39 经验总结：不同场景，如何使用线程池     
                    </div>
                    <div class="art-info clearfix">
                        
                        <span class="l">
                            更新时间：2019-11-21 10:55:44
                        </span>
                    </div>
                    <div class="art-top">
                                                <img src="https://img2.mukewang.com/5dd5f4110001dea906400359.jpg" alt="">
                                                                        <div class="famous-word-box">
                            <img src="https://www.imooc.com/static/img/column/bg-l.png" alt="" class="bg1 bg">
                            <img src="https://www.imooc.com/static/img/column/bg-r.png" alt="" class="bg2 bg">
                            <div class="famous-word">人的影响短暂而微弱，书的影响则广泛而深远。<p class="author">——普希金</p></div>
                        </div>
                                            </div>
                    <div class="art-content js-lookimg">
                        <div id="article_content">
                            <div class="cl-preview-section"><h2 id="引导语" style="font-size: 30px;">引导语</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">ThreadPoolExecutor 初始化时，主要有如下几个参数：</p>
</div><div class="cl-preview-section"><pre class=" language-java"><code class="prism  language-java"><span class="token keyword">public</span> <span class="token function">ThreadPoolExecutor</span><span class="token punctuation">(</span><span class="token keyword">int</span> corePoolSize<span class="token punctuation">,</span>
                          <span class="token keyword">int</span> maximumPoolSize<span class="token punctuation">,</span>
                          <span class="token keyword">long</span> keepAliveTime<span class="token punctuation">,</span>
                          TimeUnit unit<span class="token punctuation">,</span>
                          BlockingQueue<span class="token operator">&lt;</span>Runnable<span class="token operator">&gt;</span> workQueue<span class="token punctuation">,</span>
                          ThreadFactory threadFactory<span class="token punctuation">,</span>
                          RejectedExecutionHandler handler<span class="token punctuation">)</span> <span class="token punctuation">{</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">大家对这几个参数应该都很熟悉了，虽然参数很少，但实际工作中却有很多门道，大多数的问题主要集中在线程大小的设置，队列大小的设置两方面上，接下来我们一起看看工作中，如何初始化 ThreadPoolExecutor。</p>
</div><div class="cl-preview-section"><h2 id="coresize--maxsize" style="font-size: 30px;">1 coreSize == maxSize</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我相信很多人都看过，或自己写过这样的代码：</p>
</div><div class="cl-preview-section"><pre class=" language-java"><code class="prism  language-java">ThreadPoolExecutor executor <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ThreadPoolExecutor</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">,</span> 600000L<span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>DAYS<span class="token punctuation">,</span>
                                                     <span class="token keyword">new</span> <span class="token class-name">LinkedBlockingQueue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">这行代码主要展示了在初始化 ThreadPoolExecutor 的时候，coreSize 和 maxSize 是相等的，这样设置的话，随着请求的不断增加，会是这样的现象：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">请求数 &lt; coreSize 时，新增线程；</li>
<li style="font-size: 20px; line-height: 38px;">请求数 &gt;= coreSize &amp;&amp; 队列不满时，添加任务入队；</li>
<li style="font-size: 20px; line-height: 38px;">队列满时，此时因为 coreSize 和 maxSize 相等，任务会被直接拒绝。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">这么写的最大目的：是想让线程一下子增加到 maxSize，并且不要回收线程，防止线程回收，避免不断增加回收的损耗，一般来说业务流量都有波峰低谷，在流量低谷时，线程不会被回收；流量波峰时，maxSize 的线程可以应对波峰，不需要慢慢初始化到 maxSize 的过程。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">这样设置有两个前提条件：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">allowCoreThreadTimeOut 我们采取默认 false，而不会主动设置成 true，allowCoreThreadTimeOut 是 false 的话，当线程空闲时，就不会回收核心线程；</li>
<li style="font-size: 20px; line-height: 38px;">keepAliveTime 和 TimeUnit 我们都会设置很大，这样线程空闲的时间就很长，线程就不会轻易的被回收。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们现在机器的资源都是很充足的，我们不用去担心线程空闲会浪费机器的资源，所以这种写法目前是很常见的。</p>
</div><div class="cl-preview-section"><h2 id="maxsize-无界--synchronousqueue" style="font-size: 30px;">2 maxSize 无界 + SynchronousQueue</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在线程池选择队列时，我们也会看到有同学选择 SynchronousQueue，SynchronousQueue 我们在 《SynchronousQueue 源码解析》章节有说过，其内部有堆栈和队列两种形式，默认是堆栈的形式，其内部是没有存储的容器的，放元素和拿元素是一一对应的，比如我使用 put 方法放元素，如果此时没有对应的 take 操作的话，put 操作就会阻塞，需要有线程过来执行 take 操作后，put 操作才会返回。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">基于此特点，如果要使用 SynchronousQueue 的话，我们需要尽量将 maxSize 设置大一点，这样就可以接受更多的请求。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">假设我们设置 maxSize 是 10 的话，选择 SynchronousQueue 队列，假设所有请求都执行 put 操作，没有请求执行 take 操作，前 10 个 put 请求会消耗 10 个线程，都阻塞在 put 操作上，第 11 个请求过来后，请求就会被拒绝，所以我们才说尽量把 maxSize 设置大一点，防止请求被拒绝。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">maxSize 无界 + SynchronousQueue 这样的组合方式优缺点都很明显：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">优点：当任务被消费时，才会返回，这样请求就能够知道当前请求是已经在被消费了，如果是其他的队列的话，我们只知道任务已经被提交成功了，但无法知道当前任务是在被消费中，还是正在队列中堆积。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">缺点：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">比较消耗资源，大量请求到来时，我们会新建大量的线程来处理请求；</li>
<li style="font-size: 20px; line-height: 38px;">如果请求的量难以预估的话，maxSize 的大小也很难设置。</li>
</ol>
</div><div class="cl-preview-section"><h2 id="maxsize-有界--queue-无界" style="font-size: 30px;">3 maxSize 有界 + Queue 无界</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在一些对实时性要求不大，但流量忽高忽低的场景下，可以使用 maxSize 有界 + Queue 无界的组合方式。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">比如我们设置 maxSize 为 20，Queue 选择默认构造器的 LinkedBlockingQueue，这样做的优缺点如下：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">优点：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">电脑 cpu 固定的情况下，每秒能同时工作的线程数是有限的，此时开很多的线程其实也是浪费，还不如把这些请求放到队列中去等待，这样可以减少线程之间的 CPU 的竞争；</li>
<li style="font-size: 20px; line-height: 38px;">LinkedBlockingQueue 默认构造器构造出来的链表的最大容量是 Integer 的最大值，非常适合流量忽高忽低的场景，当流量高峰时，大量的请求被阻塞在队列中，让有限的线程可以慢慢消费。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">缺点：流量高峰时，大量的请求被阻塞在队列中，对于请求的实时性难以保证，所以当对请求的实时性要求较高的场景，不能使用该组合。</p>
</div><div class="cl-preview-section"><h2 id="maxsize-有界--queue-有界" style="font-size: 30px;">4 maxSize 有界 + Queue 有界</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">这种组合是对 3 缺点的补充，我们把队列从无界修改成有界，只要排队的任务在要求的时间内，能够完成任务即可。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">这种组合需要我们把线程和队列的大小进行配合计算，保证大多数请求都可以在要求的时间内，有响应返回。</p>
</div><div class="cl-preview-section"><h2 id="keepalivetime-设置无穷大" style="font-size: 30px;">5 keepAliveTime 设置无穷大</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">有些场景下我们不想让空闲的线程被回收，于是就把 keepAliveTime  设置成 0，实际上这种设置是错误的，当我们把 keepAliveTime 设置成 0 时，线程使用 poll 方法在队列上进行超时阻塞时，会立马返回 null，也就是空闲线程会立马被回收。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">所以如果我们想要空闲的线程不被回收，我们可以设置 keepAliveTime 为无穷大值，并且设置 TimeUnit 为时间的大单位，比如我们设置 keepAliveTime 为 365，TimeUnit 为 TimeUnit.DAYS，意思是线程空闲 1 年内都不会被回收。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在实际的工作中，机器的内存一般都够大，我们合理设置 maxSize 后，即使线程空闲，我们也不希望线程被回收，我们常常也会设置 keepAliveTime  为无穷大。</p>
</div><div class="cl-preview-section"><h2 id="线程池的公用和独立" style="font-size: 30px;">6 线程池的公用和独立</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在实际工作中，某一个业务下的所有场景，我们都不会公用一个线程池，一般有以下几个原则：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">查询和写入不公用线程池，互联网应用一般来说，查询量远远大于写入的量，如果查询和写入都要走线程池的话，我们一定不要公用线程池，也就是说查询走查询的线程池，写入走写入的线程池，如果公用的话，当查询量很大时，写入的请求可能会到队列中去排队，无法及时被处理；</li>
<li style="font-size: 20px; line-height: 38px;">多个写入业务场景看情况是否需要公用线程池，原则上来说，每个业务场景都独自使用自己的线程池，绝不共用，这样在业务治理、限流、熔断方面都比较容易，一旦多个业务场景公用线程池，可能就会造成业务场景之间的互相影响，现在的机器内存都很大，每个写入业务场景独立使用自己的线程池也是比较合理的；</li>
<li style="font-size: 20px; line-height: 38px;">多个查询业务场景是可以公用线程池的，查询的请求一般来说有几个特点：查询的场景多、rt 时间短、查询的量比较大，如果给每个查询场景都弄一个单独的线程池的话，第一个比较耗资源，第二个很难定义线程池中线程和队列的大小，比较复杂，所以多个相似的查询业务场景是可以公用线程池的。</li>
</ol>
</div><div class="cl-preview-section"><h2 id="如何算线程大小和队列大小" style="font-size: 30px;">7 如何算线程大小和队列大小</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在实际的工作中，我们使用线程池时，需要慎重考虑线程的大小和队列的大小，主要从几个方面入手：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">根据业务进行考虑，初始化线程池时，我们需要考虑所有业务涉及的线程池，如果目前所有的业务同时都有很大流量，那么在对于当前业务设置线程池时，我们尽量把线程大小、队列大小都设置小，如果所有业务基本上都不会同时有流量，那么就可以稍微设置大一点；</li>
<li style="font-size: 20px; line-height: 38px;">根据业务的实时性要求，如果实时性要求高的话，我们把队列设置小一点，coreSize ==  maxSize，并且设置 maxSize 大一点，如果实时性要求低的话，就可以把队列设置大一点。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">假设现在机器上某一时间段只会运行一种业务，业务的实时性要求较高，每个请求的平均 rt 是 200ms，请求超时时间是 2000ms，机器是 4 核 CPU，内存 16G，一台机器的 qps 是 100，这时候我们可以模拟一下如何设置：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">4 核 CPU，假设 CPU 能够跑满，每个请求的 rt 是 200ms，就是 200 ms 能执行 4 条请求，2000ms 内能执行 2000/200 * 4 = 40 条请求；</li>
<li style="font-size: 20px; line-height: 38px;">200 ms 能执行 4 条请求，实际上 4 核 CPU 的性能远远高于这个，我们可以拍脑袋加 10 条，也就是说 2000ms 内预估能够执行 50 条；</li>
<li style="font-size: 20px; line-height: 38px;">一台机器的 qps 是 100，此时我们计算一台机器 2 秒内最多处理 50 条请求，所以此时如果不进行 rt 优化的话，我们需要加至少一台机器。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">线程池可以大概这么设置：</p>
</div><div class="cl-preview-section"><pre class=" language-java"><code class="prism  language-java">ThreadPoolExecutor executor <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ThreadPoolExecutor</span><span class="token punctuation">(</span><span class="token number">15</span><span class="token punctuation">,</span> <span class="token number">15</span><span class="token punctuation">,</span> 365L<span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>DAYS<span class="token punctuation">,</span>
                                                     <span class="token keyword">new</span> <span class="token class-name">LinkedBlockingQueue</span><span class="token punctuation">(</span><span class="token number">35</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">线程数最大为 15，队列最大为 35，这样机器差不多可以在 2000ms 内处理最大的请求 50 条，当然根据你机器的性能和实时性要求，你可以调整线程数和队列的大小占比，只要总和小于 50 即可。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">以上只是很粗糙的设置，在实际的工作中，还需要根据实际情况不断的观察和调整。</p>
</div><div class="cl-preview-section"><h2 id="总结" style="font-size: 30px;">总结</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">线程池设置非常重要，我们尽量少用 Executors 类提供的各种初始化线程池的方法，多根据业务的量，实时性要求来计算机器的预估承载能力，设置预估的线程和队列大小，并且根据实时请求不断的调整线程池的大小值。</p>
</div>}
                        </div>
                    </div>
                                            <!-- 买过的阅读 -->
                        <div class="art-next-prev clearfix">
                                                                                                <!-- 已买且开放 或者可以试读 -->
                                    <a href="/read/47/article/880">
                                                                    <div class="prev l clearfix">
                                        <div class="icon l">
                                            <i class="imv2-arrow3_l"></i>
                                        </div>
                                        <p>
                                            38 线程池源码面试题    
                                        </p>
                                    </div>
                                </a>
                                                                                                                            <!-- 已买且开放 或者可以试读 -->
                                    <a href="/read/47/article/882">
                                                                    <div class="next r clearfix">
                                        <p>
                                            40 打动面试官：线程池流程编排中的运用实战   
                                        </p>
                                        <div class="icon r">
                                            <i class="imv2-arrow3_r"></i>
                                        </div>

                                    </div>
                                </a>
                                                    </div>
                                    </div>
                <div class="comments-con js-comments-con" id="coments_con">
                </div>

                
            </div>
            
            
            

        </div>
    </div>
</div>

<div class="modal modal-jiaQun-new hide" id="modal-jiaQun">
    <div class="inner" style="">
        <div class="modal-close js-close-jiaQun">
            <i class="imv2-close"></i>
        </div>
        <div class="content">
            <img src="https://img3.mukewang.com/5d634d40000119e505400602.jpg">
            <div class="right-info">
                <div class="title">
                    扫码加入慕课Java核心用户群
                </div>
                <div class="desc">
                                            <p class="mb6">验证信息：<span id="joincode">1909271435058473</span><span class="copy js-copy-joincode">复制</span></p>
                                        <p class="mb6">QQ讨论群号：906691736</p>
                                            <p>QQ群URL：<a href="https://jq.qq.com/?_wv=1027&amp;k=55RtSbJ" target="_blank">点击访问</a></p>
                                    </div>
            </div>
            <p class="tip">若遇到搜索不到QQ群或加群失败，请联系客服邮箱:kf@imooc.com</p>
        </div>
    </div>
</div>
 
<!-- 专栏介绍页专栏评价 -->

<!-- 专栏介绍页底部三条评价 -->

<!-- 专栏阅读页弹层目录和介绍页页面目录 -->

<!-- 专栏阅读页发布回复 -->

<!-- 专栏阅读页发布评论 -->

<!-- 专栏阅读页底部评论 -->

<!-- 专栏阅读 单个 评论 -->

<!-- 新增回复和展开三条以外回复 -->

<!-- 立即订阅的弹窗 -->












</div></body></html>